﻿// Bravura : A music composition and audio synthesis framework.
//
// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xaml;
using Bravura.Core.PluginLib;
using Bravura.Core.SynthLib.Model;
using Bravura.Core.Common;

namespace Bravura.Core.SynthLib
{
	/// <summary>
	/// Synth engine implementation.
	/// </summary>
	public class SynthEngine : ISynthEngine, IAudioPluginHost
	{
		#region Fields

		private ISynthEngineHost _host = null;
		private int _sampleRate = 48000;
		private SynthProject _project = null;
		private List<PluginProxy> _itemList = new List<PluginProxy>();
		private Dictionary<int, PluginProxy> _itemDic = new Dictionary<int, PluginProxy>();
		private string _homeDir = null;

		#endregion

		/// <summary>
		/// Create a new SynthEngine instance.
		/// </summary>
		public SynthEngine(ISynthEngineHost host, int sampleRate)
		{
			_host = host;
			_sampleRate = sampleRate;
		}

		#region ISynthEngine Implementation

		/// <summary>
		/// Load a project from disk.
		/// </summary>
		public void Load(FileInfo projectFile)
		{
			object obj = XamlServices.Load(projectFile.FullName);
			if (obj is SynthProject)
			{
				string oldDir = Directory.GetCurrentDirectory();
				Directory.SetCurrentDirectory(_homeDir = projectFile.DirectoryName);
				this.Load(obj as SynthProject);
				Directory.SetCurrentDirectory(oldDir);
			}
		}

		/// <summary>
		/// Load a project from a SynthProject object.
		/// </summary>
		public void Load(SynthProject project)
		{
			_project = project;

			// sort the plugins
			_project.Plugins.Sort((PluginInfo x, PluginInfo y) => { return x.ProcessOrder - y.ProcessOrder; });

			// create / initialize / add plugins
			foreach (PluginInfo info in _project.Plugins)
			{
				Type type = Type.GetType(info.TypeName);
				if (type != null)
				{
					// create the plugin instance
					IAudioPlugin plugin = Activator.CreateInstance(type, this as IAudioPluginHost, _sampleRate) as IAudioPlugin;
					if (plugin != null)
					{
						// configure the plugin
						plugin.Configure(info.Configuration);

						// wrap it in a proxy object
						PluginProxy proxy = new PluginProxy { Type = type, Instance = plugin };

						// add proxy to dictionary and list
						_itemDic[info.Num] = proxy;
						_itemList.Add(proxy);
					}
				}
			}

			// add connections
			foreach (Connection conn in _project.Connections)
			{
				// get the source and target proxies
				PluginProxy source = _itemDic[conn.SourceItemNum];
				PluginProxy target = _itemDic[conn.TargetItemNum];

				// add audio routes
				foreach (RouteInfo info in conn.Routes)
				{
					// connect them
					if (source != null && target != null)
					{
						PluginProxy.Connect(source, info.SourceChannel, target, info.TargetChannel);
					}
				}
			}
		}

		/// <summary>
		/// Returns the requested plugin.
		/// </summary>
		public IAudioPlugin GetPlugin(int pluginNum)
		{
			return _itemDic[pluginNum].Instance;
		}

		/// <summary>
		/// Open the engine for use.
		/// </summary>
		public void Open()
		{
			string oldDir = Directory.GetCurrentDirectory();
			Directory.SetCurrentDirectory(_homeDir);

			// open all plugins
			foreach (var item in _itemList)
			{
				item.Instance.Open();
			}

			Directory.SetCurrentDirectory(oldDir);
		}

		/// <summary>
		/// Run the engine for "count" samples.
		/// </summary>
		public void Process(int count)
		{
			// zero all connected input buffers
			foreach (var item in _itemList)
			{
				item.ZeroInputs(count);
			}

			// process plugins
			foreach (var item in _itemList)
			{
				item.Instance.Process(count);
			}

			// zero all connected output buffers
			foreach (var item in _itemList)
			{
				item.ZeroOutputs(count);
			}
		}

		/// <summary>
		/// Close the engine out after use.
		/// </summary>
		public void Close()
		{
			// close all plugins
			foreach (var item in _itemList)
			{
				item.Instance.Close();
			}
		}

		/// <summary>
		/// Sets the gain of one input channel of a plugin.
		/// </summary>
		public void SetInputGain(int pluginNum, int channel, double gain)
		{
			_itemDic[pluginNum].SetInputGain(channel, gain);
		}

		/// <summary>
		/// Sets the gain of one output channel of a plugin.
		/// </summary>
		public void SetOutputGain(int pluginNum, int channel, double gain)
		{
			_itemDic[pluginNum].SetOutputGain(channel, gain);
		}

		#endregion

		#region IAudioPluginHost Implementation

		/// <summary>
		/// Allows an audio plugin to log a message.
		/// </summary>
		public void LogMessage(IAudioPlugin source, TraceLevel severity, string message)
		{
			if (_host != null)
			{
				_host.LogMessage(this, severity, message);
			}
		}

		/// <summary>
		/// Returns the output path.
		/// </summary>
		public DirectoryInfo GetOutputPath()
		{
			return _host.GetOutputPath();
		}

		/// <summary>
		/// Returns the tempo (in bpm).
		/// </summary>
		public double GetTempo()
		{
			return _host.GetTempo();
		}

		#endregion
	}
}
